﻿using System;
using System.Collections.Generic;
using System.Text;

/*
Todo:
8. Drag and drop - big ask.
*/

/*
	The Controller class of a model/view/controller pattern
	The goal is that the view class contains no logic that acts on
	the model. It only binds controls to model data.
	the controller class has no idea about the controls on the interface
	and the model class is purely a data-store with no logic.

	To achieve this we are binding properties of the model to 
	controls in the view via the controller. 
*/

namespace SqlServerDriver
{
	// Model class for the dialog. Rule is only data and validation
	// implement equals and copy
	internal class ObjectListModel
	{
		// props must be public to be bound to controls
		public List<string> ItemsSelected {get { return l1; } }
		public List<string> l1 = new List<string>();

		// > Boilerplate: CopyFrom to the controller can make a working
		// copy from what it was sent in the constructor.
		// Equal method is for checking for changes.
		internal void CopyFrom(ObjectListModel source)
		{
			this.ItemsSelected.Clear();
			this.ItemsSelected.AddRange(source.ItemsSelected);
		}

		public override bool Equals(Object target)
		{
			// object is wrong tyoe
			ObjectListModel targetModel = target as ObjectListModel;
			
			// Note the equality comparison for the list.
			//if (!this.ItemsSelected.SequenceEqual(targetModel.ItemsSelected))
			//    return false;
			if (!ListsAreEqual(this.ItemsSelected, targetModel.ItemsSelected))
				return false;
			// test other properties here.
			return true;
		}

		private bool ListsAreEqual(List<string> l1, List<string> l2)
		{
			if (l1.Count != l2.Count) return false;
			int idx = 0;
			foreach (string s1 in l1)
			{
				if (!s1.Equals(l2[idx++]))
					return false;
			}
			return true;
		}

		// I think only in cast the object is used in a dictionary
		// Probably not necessary for a model object.
		public override int GetHashCode()
		{
			return this.ToString().GetHashCode();
		}

		// debug only Normally
		public override string ToString()
		{
			string memo = "";
			foreach (string pi in this.ItemsSelected)
				memo += string.Format("{0}\n", pi);
			return string.Format("{0}", memo);
		}

	}

	class SelectObjectListController
	{
		// > Boilerplate: references to objects of the pattern
		// keep the original safe until the Save button is pressed
		// compare with the new to set the "dirty" flag 
		public ObjectListModel model;
		public ObjectListModel newModel;

		// friendly name for the model for the view to access
		public ObjectListModel Model
		{
			get { return newModel; }
		}

		// reference the view as there are messages to pass from one to the other
		public SelectObjectListView View { get; set; }
		// < Boilerplate: references to objects of the pattern.

		
		// objects to be bound to controls in the view
		// Note: normally the model properties are bound,
		// but for collection controls it is slightly more involved
		
		// selectionList preserves a full copy of all the selectable
		// items. The list displayed though is altered by the past selections
		// and any filtering applied to a long list.
		List<string> selectionList = new List<string>();

		// objects to bind to the current list control selections
		public string SourceSelection { get; set; }
		public List<string> SourceSelections {get; set;}
		public string SelectedSelection { get; set; }
		public string Filter {get; set;}

		// Constructor
		//
		public SelectObjectListController(ObjectListModel modelIn)
		{
			// > Boilerplate: Constructor work
			// hold a reference to the original model. 
			this.model = modelIn;
			// create a working copy of the original
			this.newModel = new ObjectListModel();
			this.newModel.CopyFrom(this.model);
			// < Boilerplate: Constructor work
			this.SourceSelections = new List<string>();
		}

		// The view calls this either at the end of construction or (better)
		// when the form is loaded, just before it is displayed.
		// Its a fairly standard method, but specialised for each dialog.
		public void SetFormInitialState()
		{
			this.Filter = "";
			// find items to display in the source list
			List<string> source = this.FilterTheSelectionList(this.Filter);
			// set the item to select. If none then we cannot. If some then set to 1st
			if (source.Count > 0)
				this.SourceSelection = source[0];
			
			// get the view to respect this
			this.View.RefreshSource(source);
			
			// now the selections for the case where the dialog is opened with some items already selected.
			source = this.newModel.ItemsSelected;
			if (source.Count > 0)
				this.SelectedSelection = source[0];
			this.View.RefreshSelected(source);
		}

		// > Boilerplate: Have changes been made to the working copy of the model?
		public bool IsDirty()
		{
			return !(newModel.Equals(model));
		}
		
		// Either called from the view or from the controller
		// This simple example does not require a validation step.
		public bool CommitChangesToModel()
		{
			// validate and return false if necessary
			bool IsValid = true;
			if (IsValid)
				this.model.CopyFrom(this.newModel);
			return IsValid;
		}

		public string GetLastError()
		{
			return "";
		}
		// < Boilerplate.
	
		///////////////////////////////////////////////////////////////////////////////////////////////
		// Specialised logic for this MVC instance

	
		///////////////////////////////////////////////////////////////////////////////////////////////
		// Configuration: Inject the list of items for selection data.
		internal void SetSelectionList(List<string> pick2)
		{
			this.selectionList = pick2;
		}
		
		///////////////////////////////////////////////////////////////////////////////////////////////
		// API for the view to consume. 
		// Add button pressed
		internal void OnAdd()
		{
			string pi = this.SourceSelection;
			OnAddOne(pi);
		}

		// adding a many
		internal void OnAddOne(string pi)
		{
			if (pi != null)
			{
				// get current list applicable to the source list (this is necessary here
				// to work out the correct item to set the cursor at.)
				List<string> source = this.FilterTheSelectionList(this.Filter);
				// add the selection to the selected list
				this.newModel.ItemsSelected.Add(pi);
				// work out the new source selected item based on the item to be remove
				string piSel = FindNewSelection(source, pi);
				// remove it
				source.Remove(pi);
				// set the new selection *before* refreshing the view with the adjusted list
				this.SourceSelection = piSel;
				this.View.RefreshSource(source);

				// for the selected items we need to refresh the controls to redisplay the list
				this.SelectedSelection = pi;
				this.View.RefreshSelected(this.newModel.ItemsSelected);
			}
		}

		// Delete button pressed
		internal void OnDelete()
		{
			string pi = this.SelectedSelection;
			this.OnDeleteOne(pi);
		}

		internal void OnDeleteOne(string pi)
		{
			if (pi != null)
			{
				// work out which item to select when the current item is removed
				string piSel = FindNewSelection(this.newModel.ItemsSelected, pi);
				// remove it.
				this.newModel.ItemsSelected.Remove(pi);
				// for the selected items we need to refresh the controls to redisplay the list
				this.SelectedSelection = piSel;
				this.View.RefreshSelected(this.newModel.ItemsSelected);

				// set the selection on the source list to be the item just added back
				this.SourceSelection = pi;
				this.View.RefreshSource(this.FilterTheSelectionList(this.Filter));
			}
		}



		internal void OnFilterChanged()
		{
			// find items to display in the source list
			List<string> source = this.FilterTheSelectionList(this.Filter);
			// set the item to select. If none then we cannot. If some then set to 1st
			if (source.Count > 0)
				this.SourceSelection = source[0];
			
			// get the view to respect this
			this.View.RefreshSource(source);
		}

		internal void ClearFilter()
		{
			if (this.Filter.Length > 0)
			{
				// this.Filter = "";
				OnFilterChanged();
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////
		// Helpers

		// remove items based on a name filter
		private List<string> FilterTheSelectionList(string filter)
		{
			// create a list to act as a new data-source
			List<string> filteredList = new List<string>();
			// check each item in the full list
			foreach (string pi in this.selectionList)
			{
				// remove if in target list
				if (this.newModel.ItemsSelected.Contains(pi))
					continue;
				// remove if the filter is active
				if (string.IsNullOrEmpty(filter) ||
					pi.ToUpper().Contains(filter.ToUpper()))
				{
					filteredList.Add(pi);
				}
			}
			return filteredList;
		}

		// figure out the selected item from the one just to be removed.
		private string FindNewSelection(List<string> list, string pi)
		{
			int delIdx = list.IndexOf(pi);
			bool isLastItem = (delIdx == list.Count - 1);
			// if not at end of list the item after the one to be deleted will be selected after
			// this keeps the cursor in position
			if (!isLastItem)
				return list[delIdx + 1];
			// if last item is the only one the don't set selection
			// else select the new last item
			return (delIdx == 0) ? null : list[delIdx - 1]; 
		}


	}
}
